﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace FuncWorks.Xna.UI
{
	public class CoverFlowSelector : IDisposable
	{
		#region Classes
		class Cover
		{
			public Texture2D Texture { get; set; }

			Vector3 position;
			public Vector3 Position
			{
				get { return position; }
				set { position = value; }
			}
			

			public float Angle { get; set; }

			Vector3 TargetPosition;
			Vector3 TotalMove;

			float TargetAngle;
			float TotalTurn;

			int AnimationTime;
			int Duration;

			public bool IsAnimationComplete;

			public Cover()
			{
				position = Vector3.Zero;
				TargetPosition = Vector3.Zero;
				TotalMove = Vector3.Zero;
				IsAnimationComplete = true;
			}

			public void StartAnimation(float targetX, float targetY, float targetZ, float endAngle, int duration)
			{
				TargetPosition.X = targetX;
				TargetPosition.Y = targetY;
				TargetPosition.Z = targetZ;
				TargetAngle = endAngle;

				TotalMove.X = TargetPosition.X - position.X;
				TotalMove.Y = TargetPosition.Y - position.Y;
				TotalMove.Z = TargetPosition.Z - position.Z;
				TotalTurn = TargetAngle - Angle;

				Duration = duration;
				AnimationTime = 0;
				IsAnimationComplete = false;
			}

			public void ApplyAnimation(GameTime gameTime)
			{
				if (IsAnimationComplete) { return; }

				AnimationTime += gameTime.ElapsedGameTime.Milliseconds;
				if (AnimationTime > Duration)
				{
					IsAnimationComplete = true;
					position.X = TargetPosition.X;
					position.Y = TargetPosition.Y;
					position.Z = TargetPosition.Z;
					Angle = TargetAngle;
				}
				else
				{
					float pct = 1 - (AnimationTime / (float)Duration);
					position.X = TargetPosition.X - (TotalMove.X * pct);
					position.Y = TargetPosition.Y - (TotalMove.Y * pct);
					position.Z = TargetPosition.Z - (TotalMove.Z * pct);
					Angle = TargetAngle - (TotalTurn * pct);
				}
			}
		}
		#endregion

		#region Public Properties
		public int SelectedIndex { get; set; }

		public int FlowDurationInMilliseconds { get; set; }
		public int ShiftDurationInMilliseconds { get; set; }

		public float StackDistance { get; set; }
		public float StackDepth { get; set; }

		public Vector3 SelectedCenter { get; set; }
        public Vector3 LeftStackStart { get; set; }
		public Vector3 RightStackStart { get; set; }

        public float LeftStackAngle { get; set; }
        public float RightStackAngle { get; set; }
		#endregion

		#region Fields / Constants	
		List<Cover> covers = new List<Cover>();
		BasicEffect textureEffect;
		Matrix view;
		Matrix projection;
		VertexPositionColorTexture[] coverVertices = new VertexPositionColorTexture[4];
		const float BOUNDARY = 4.0f;
		const float SCALAR = 0.08f;
		#endregion

		#region IDisposable
		public void Dispose()
		{
			GC.SuppressFinalize(this);
			if (textureEffect != null) { textureEffect.Dispose(); }
		}
		#endregion

		#region Setup
		public CoverFlowSelector(float aspectRatio)
		{
			SelectedIndex = 0;

			FlowDurationInMilliseconds = 500;
			ShiftDurationInMilliseconds = 100;
			
			StackDistance = 0.1f;
			StackDepth = 0.1f;
			
			SelectedCenter = new Vector3(0, 0, 3);

			LeftStackStart = new Vector3(-1.5f, 0, 0.5f);
			RightStackStart = new Vector3(1.5f, 0, 0.5f);
			
			LeftStackAngle = MathHelper.ToRadians(70);
			RightStackAngle = MathHelper.ToRadians(-70);

			InitCamera(aspectRatio);
		}

		public void AddCover(Texture2D texture)
		{
			covers.Add(new Cover() { Texture = texture });
		}

		public void SetInitialPositions()
		{
            InitSurface();
			for (int i = 0; i < covers.Count; i++)
			{
				Cover c = covers[i];

				// Just to the left
				if (i < SelectedIndex)
				{
					int diff = (SelectedIndex - i) - 1;
					c.Angle = LeftStackAngle;
					c.Position = new Vector3(
						LeftStackStart.X - (StackDistance * diff),
						LeftStackStart.Y,
						LeftStackStart.Z - (StackDepth * diff)
					);
				}
				else if (i == SelectedIndex)
				{
					c.Angle = 0;
					c.Position = new Vector3(
						SelectedCenter.X,
						SelectedCenter.Y,
						SelectedCenter.Z
					);
				}
				else
				{
					int diff = (i - SelectedIndex) - 1;
					c.Angle = RightStackAngle;
					c.Position = new Vector3(
						RightStackStart.X + (StackDistance * diff),
						RightStackStart.Y,
						RightStackStart.Z - (StackDepth * diff)
					);
				}
			}
		}
		#endregion

		#region Update / Draw
		public void Update(GameTime gameTime)
		{
			foreach (Cover cover in covers) { cover.ApplyAnimation(gameTime); }
		}

		public void Draw(GraphicsDeviceManager graphics)
		{
            DrawCover(graphics, SelectedIndex);
            for (int i = 1; i < covers.Count; i++)
            {
                int toLeft = SelectedIndex - i;
                int toRight = SelectedIndex + i;
                if (toLeft >= 0) { DrawCover(graphics, toLeft); }
                if (toRight < covers.Count) { DrawCover(graphics, toRight); }
                if (toLeft < 0 && toRight >= covers.Count) { break; }

            }
		}
		#endregion

		#region Scrolling
		public void MoveLeft()
		{
			// See if valid
			if (SelectedIndex <= 0) { return; }
			if ((from c in covers where !c.IsAnimationComplete select c).Count<Cover>() > 0) { return; }

			// Change index
			SelectedIndex--;

			for (int i = 0; i < covers.Count; i++)
			{
				Cover c = covers[i];

				if (i == SelectedIndex)
				{
					c.StartAnimation(SelectedCenter.X, SelectedCenter.Y, SelectedCenter.Z, 0, FlowDurationInMilliseconds);
				}
				else if (i == SelectedIndex + 1)
				{
					c.StartAnimation(RightStackStart.X, RightStackStart.Y, RightStackStart.Z, RightStackAngle, FlowDurationInMilliseconds);
				}
				else if (i < SelectedIndex)
				{
					int diff = (SelectedIndex - i) - 1;
					c.StartAnimation(LeftStackStart.X - (diff * StackDistance), LeftStackStart.Y, LeftStackStart.Z - (diff * StackDepth), LeftStackAngle, ShiftDurationInMilliseconds);
				}
				else if (i > SelectedIndex)
				{
					int diff = (i - SelectedIndex) - 1;
					c.StartAnimation(RightStackStart.X + (diff * StackDistance), RightStackStart.Y, RightStackStart.Z - (diff * StackDepth), RightStackAngle, ShiftDurationInMilliseconds);
				}
			}
		}

		public void MoveRight()
		{
			// See if valid
			if (SelectedIndex >= covers.Count - 1) { return; }
			if ((from c in covers where !c.IsAnimationComplete select c).Count<Cover>() > 0) { return; }

			// Change index
			SelectedIndex++;

			for (int i = 0; i < covers.Count; i++)
			{
				Cover c = covers[i];

				if (i == SelectedIndex)
				{
					c.StartAnimation(SelectedCenter.X, SelectedCenter.Y, SelectedCenter.Z, 0, FlowDurationInMilliseconds);
				}
				else if (i == SelectedIndex - 1)
				{
					c.StartAnimation(LeftStackStart.X, LeftStackStart.Y, LeftStackStart.Z, LeftStackAngle, FlowDurationInMilliseconds);
				}
				else if (i < SelectedIndex)
				{
					int diff = (SelectedIndex - i) - 1;
					c.StartAnimation(LeftStackStart.X - (diff * StackDistance), LeftStackStart.Y, LeftStackStart.Z - (diff * StackDepth), LeftStackAngle, ShiftDurationInMilliseconds);
				}
				else if (i > SelectedIndex)
				{
					int diff = (i - SelectedIndex) - 1;
					c.StartAnimation(RightStackStart.X + (diff * StackDistance), RightStackStart.Y, RightStackStart.Z - (diff * StackDepth), RightStackAngle, ShiftDurationInMilliseconds);
				}
			}
		}
		#endregion

		#region Graphics
		protected void InitCamera(float aspectRatio)
		{
			projection = Matrix.CreatePerspectiveFieldOfView(
				MathHelper.ToRadians(35),
				aspectRatio,
				1.0f, 10000.0f);

			view = Matrix.CreateLookAt(new Vector3(0, 0, 5), new Vector3(0, 0, 0), Vector3.Up);
		}

		protected void InitSurface()
		{
			Vector3 pos = new Vector3(0.0f, 0.0f, 0.0f);
			Vector2 textureUV = new Vector2(0, 0);
			Color color = Color.White;

			// A. top left, B. bottom left, C. top right, D. bottom right
			textureUV.X = 0.0f; textureUV.Y = 1.0f; pos.X = -BOUNDARY; pos.Y = -BOUNDARY; pos.Z = 0;
			coverVertices[0] = new VertexPositionColorTexture(pos, color, textureUV);//A

			textureUV.X = 0.0f; textureUV.Y = 0.0f; pos.X = -BOUNDARY; pos.Y = BOUNDARY; pos.Z = 0;
			coverVertices[1] = new VertexPositionColorTexture(pos, color, textureUV);//B

			textureUV.X = 1.0f; textureUV.Y = 1.0f; pos.X = BOUNDARY; pos.Y = -BOUNDARY; pos.Z = 0;
			coverVertices[2] = new VertexPositionColorTexture(pos, color, textureUV);//C

			textureUV.X = 1.0f; textureUV.Y = 0.0f; pos.X = BOUNDARY; pos.Y = BOUNDARY; pos.Z = 0;
			coverVertices[3] = new VertexPositionColorTexture(pos, color, textureUV);//D
		}

		protected void DrawCover(GraphicsDeviceManager graphics, int index)
		{
			if (textureEffect == null) { textureEffect = new BasicEffect(graphics.GraphicsDevice); }

			Cover c = covers[index];

			Matrix world, scale, translation, rotationY;

			scale = Matrix.CreateScale(SCALAR, SCALAR, SCALAR);
			rotationY = Matrix.CreateRotationY(c.Angle);
			translation = Matrix.CreateTranslation(c.Position);

			textureEffect.TextureEnabled = true;
			textureEffect.Texture = c.Texture;

			world = scale * rotationY * translation;
			textureEffect.World = world;
			textureEffect.View = view;
			textureEffect.Projection = projection;

			graphics.GraphicsDevice.BlendState = BlendState.AlphaBlend;

			VertexBuffer vb = new VertexBuffer(graphics.GraphicsDevice, typeof(VertexPositionColorTexture), 4, BufferUsage.None);
			vb.SetData(coverVertices);
			graphics.GraphicsDevice.SetVertexBuffer(vb);

			foreach (EffectPass pass in textureEffect.CurrentTechnique.Passes)
			{
				pass.Apply();
				graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColorTexture>(
					PrimitiveType.TriangleStrip,
					coverVertices,
					0,
					2);
			}
		}
		#endregion
	}
}
